﻿/*
* 绘画过程：
* 1、创建窗口时，系统发送WM_CREATE消息，创建菜单栏，初始化绘图缓冲区
* 2、用户点击菜单选项：系统发送WM_COMMAND消息，使得用户可以对菜单栏进行操作
* 3、用户鼠标左键点击时，系统发送WM_LBUTTONDOWN消息，记录鼠标起始坐标，同时激活绘图状态
* 4、用户鼠标左键抬起时，说明绘图结束了，记录结束坐标，生成绘图信息，在内存缓冲区中绘制最终的图形，并将图形信息存入列表中
* 发送重绘信息，将内存中的图一次性打印到屏幕
* 5、当鼠标光标在客户区中移动时，其实系统是一直发送WM_MOUSEMOVE信息，只是由于一直不处于
* 绘画状态，所以没有反应，而当处于绘图状态下，得先创建一个临时的缓冲区，将那一帧的绘画点画入临时缓冲区，
* 再直接打印到屏幕，否则的会“闪”，因为绘画过程中，需要对上一帧的擦除，否则就会导致一片一片的，而如果直接在最终缓冲区画，就会导致
* 其他原本画好图形在绘画过程中“闪”，因此需要双缓冲区。临时缓冲区用于绘画过程，并打印到屏幕，但是无法存储绘画结果，而最终缓冲区是在
* 绘画结束的一瞬间，通过起始点和结束点以及绘图信息，直接绘画出图形的
* * 临时缓冲区的作用是“模拟”最终缓冲区的状态，用于擦除操作。
* * 最终缓冲区始终保持已确认图形的完整性，仅在绘图结束时更新。
* 6、关闭窗口时，系统发送WM_DESTROY信息，向消息队列发送WM_QUIT消息。使得GetMessage返回0，终止信息循环
*/

#include<Windows.h>
#include<tchar.h>
#include<vector>
#include<commctrl.h>  // 用于颜色对话框

#pragma comment(lib, "comctl32.lib")

//绘图类型枚举（直线，矩形）
enum class DrawingType
{
    None,
    Line,
    Rectangle
};

//绘图信息结构体（绘图类型，坐标，颜色，线宽）
struct DrawInfo
{
    DrawingType type = DrawingType::None;
    POINT pos1 = { 0,0 };
    POINT pos2 = { 0,0 };
    COLORREF color = RGB(0, 0, 0);  // 直接使用COLORREF
    int width = 2;
};

std::vector<DrawInfo> DrawInfos;
DrawingType curType = DrawingType::Line;  // 默认绘制直线
COLORREF curColor = RGB(0, 0, 0);         // 默认黑色
int curWidth = 2;                         // 默认线宽2像素

// 全局变量
static HBITMAP hDrawingBitmap = NULL;  // 存储所有图形的内存位图（就像“画布”）
static HDC hDrawingDC = NULL;          // 对应的设备上下文（就像“画笔”）
static int drawingWidth = 0, drawingHeight = 0;  // 位图尺寸

// 颜色选择对话框相关变量
static CHOOSECOLOR cc = { 0 };
static COLORREF acrCustClr[16] = { 0 };  // 自定义颜色数组

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// 初始化颜色对话框
void InitColorDialog() {
    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = NULL;
    cc.lpCustColors = (LPDWORD)acrCustClr;
    cc.rgbResult = curColor;
    cc.Flags = CC_FULLOPEN | CC_RGBINIT;
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    // 初始化通用控件（如按钮等）
    INITCOMMONCONTROLSEX icc;
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&icc);

    //注册窗口类
    const wchar_t CLASS_NAME[] = L"SimpleDrawApp";
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);//注册窗口类

    // 初始化颜色对话框
    InitColorDialog();

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, L"简易绘图工具",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL
    );
    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);//显示窗口

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);//将按键（虚拟键）信息转为字符串，存储在wParam中，
        DispatchMessage(&msg);//根据msg.hwnd中的CALSS_NAME找到对应的类，调用该窗口类窗口过程函数（回调），把参数传递过去
    }
    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //ptStart:绘图起点，ptEnd:绘图终点，prevPos:上一次鼠标位置（连续绘图），isDrawing:是否正在绘图
    static POINT ptStart, ptEnd, prevPos;
    static bool isDrawing = false;

    switch (uMsg)
    {
        //用户主动关闭窗口，系统发送该信号，触发代码执行
    case WM_DESTROY:
    {
        // 清理资源
        if (hDrawingBitmap) {
            DeleteObject(hDrawingBitmap);
            hDrawingBitmap = NULL;
        }
        if (hDrawingDC) {
            DeleteDC(hDrawingDC);
            hDrawingDC = NULL;
        }
        //向消息队列发送WM_QUIT消息。使得GetMessage返回0，终止信息循环
        PostQuitMessage(0);
        return 0;
    }

    case WM_CREATE:
    {
        // 创建菜单栏
        HMENU hMenu = CreateMenu();

        // 创建"形状"子菜单
        HMENU hShapeMenu = CreatePopupMenu();
        AppendMenu(hShapeMenu, MF_STRING | MF_CHECKED, 1, L"直线");
        AppendMenu(hShapeMenu, MF_STRING, 2, L"矩形");

        // 创建"颜色"子菜单 - 现在只有一个"选择颜色"选项
        HMENU hColorMenu = CreatePopupMenu();
        AppendMenu(hColorMenu, MF_STRING, 3, L"选择颜色...");

        // 创建"线宽"子菜单
        HMENU hWidthMenu = CreatePopupMenu();
        AppendMenu(hWidthMenu, MF_STRING, 4, L"1px");
        AppendMenu(hWidthMenu, MF_STRING | MF_CHECKED, 5, L"2px");
        AppendMenu(hWidthMenu, MF_STRING, 6, L"3px");
        AppendMenu(hWidthMenu, MF_STRING, 7, L"5px");
        AppendMenu(hWidthMenu, MF_STRING, 8, L"10px");

        // 将子菜单添加到主菜单
        AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hShapeMenu, L"形状");
        AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hColorMenu, L"颜色");
        AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hWidthMenu, L"线宽");

        SetMenu(hwnd, hMenu);

        // 初始化绘图缓冲区

        //获取客户区长宽
        RECT rect;
        GetClientRect(hwnd, &rect);
        drawingWidth = rect.right - rect.left;
        drawingHeight = rect.bottom - rect.top;

        //创建内存绘图环境（构建一个内存绘图缓冲区）
        HDC hdc = GetDC(hwnd);
        hDrawingDC = CreateCompatibleDC(hdc);
        hDrawingBitmap = CreateCompatibleBitmap(hdc, drawingWidth, drawingHeight);
        SelectObject(hDrawingDC, hDrawingBitmap);

        // 填充背景色
        HBRUSH hbrWhite = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        FillRect(hDrawingDC, &rect, hbrWhite);
        DeleteObject(hbrWhite);

        ReleaseDC(hwnd, hdc);

        return 0;
    }

    //打印到屏幕
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // 直接从内存缓冲区复制到屏幕
        BitBlt(hdc, 0, 0, drawingWidth, drawingHeight, hDrawingDC, 0, 0, SRCCOPY);

        EndPaint(hwnd, &ps);
        return 0;
    }
    //WM_COMMAND 是 Windows 编程中的一个消息类型，用于通知窗口过程（Window Procedure）有命令事件发生。
    // 这些事件通常源自用户界面元素（如菜单、按钮、工具栏等），用于触发特定功能。
    case WM_COMMAND:
    {

        //获取当前窗口的菜单句柄
        HMENU hMenu = GetMenu(hwnd);
        //获取菜单项ID
        switch (LOWORD(wParam))
        {
        case 1:  // 选择直线
            curType = DrawingType::Line;
            CheckMenuItem(hMenu, 1, MF_CHECKED);
            CheckMenuItem(hMenu, 2, MF_UNCHECKED);
            break;

        case 2:  // 选择矩形
            curType = DrawingType::Rectangle;
            CheckMenuItem(hMenu, 2, MF_CHECKED);
            CheckMenuItem(hMenu, 1, MF_UNCHECKED);
            break;

        case 3:  // 选择颜色
            cc.hwndOwner = hwnd;
            cc.rgbResult = curColor;

            if (ChooseColor(&cc)) 
            {
                curColor = cc.rgbResult;
            }
            break;

        case 4:  // 线宽1px
            curWidth = 1;
            CheckMenuItem(hMenu, 4, MF_CHECKED);
            CheckMenuItem(hMenu, 5, MF_UNCHECKED);
            CheckMenuItem(hMenu, 6, MF_UNCHECKED);
            CheckMenuItem(hMenu, 7, MF_UNCHECKED);
            CheckMenuItem(hMenu, 8, MF_UNCHECKED);
            break;

        case 5:  // 线宽2px
            curWidth = 2;
            CheckMenuItem(hMenu, 5, MF_CHECKED);
            CheckMenuItem(hMenu, 4, MF_UNCHECKED);
            CheckMenuItem(hMenu, 6, MF_UNCHECKED);
            CheckMenuItem(hMenu, 7, MF_UNCHECKED);
            CheckMenuItem(hMenu, 8, MF_UNCHECKED);
            break;

        case 6:  // 线宽3px
            curWidth = 3;
            CheckMenuItem(hMenu, 6, MF_CHECKED);
            CheckMenuItem(hMenu, 5, MF_UNCHECKED);
            CheckMenuItem(hMenu, 4, MF_UNCHECKED);
            CheckMenuItem(hMenu, 7, MF_UNCHECKED);
            CheckMenuItem(hMenu, 8, MF_UNCHECKED);
            break;

        case 7:  // 线宽5px
            curWidth = 5;
            CheckMenuItem(hMenu, 7, MF_CHECKED);
            CheckMenuItem(hMenu, 5, MF_UNCHECKED);
            CheckMenuItem(hMenu, 6, MF_UNCHECKED);
            CheckMenuItem(hMenu, 4, MF_UNCHECKED);
            CheckMenuItem(hMenu, 8, MF_UNCHECKED);
            break;

        case 8:  // 线宽10px
            curWidth = 10;
            CheckMenuItem(hMenu, 8, MF_CHECKED);
            CheckMenuItem(hMenu, 5, MF_UNCHECKED);
            CheckMenuItem(hMenu, 6, MF_UNCHECKED);
            CheckMenuItem(hMenu, 7, MF_UNCHECKED);
            CheckMenuItem(hMenu, 4, MF_UNCHECKED);
            break;
        }
        return 0;
    }

    //lParam:低16位存储X坐标，高16位存储Y坐标，左键点击时，调整为绘画状态，记录此次为上次鼠标位置，用于连续线条绘制
    case WM_LBUTTONDOWN:
    {
        ptStart = { LOWORD(lParam), HIWORD(lParam) };
        prevPos = ptStart;
        isDrawing = true;
        SetCapture(hwnd);//捕获鼠标输入，将鼠标输入锁定到当前窗口，确保不会因为光标移出窗口中断绘图
        return 0;
    }

    //抬起鼠标左键时触发，同时表示绘图结束
    case WM_LBUTTONUP:
    {
        if (isDrawing)
        {
            isDrawing = false;//绘画结束标识
            ReleaseCapture(); // 释放鼠标捕获，允许其他窗口接收鼠标事件

            
            ptEnd = { LOWORD(lParam), HIWORD(lParam) };//获取结束坐标

            // 添加新图形到列表
            DrawInfo info;
            info.type = curType;
            info.pos1 = ptStart;
            info.pos2 = ptEnd;
            info.color = curColor;
            info.width = curWidth;
            DrawInfos.push_back(info);

            // 在内存缓冲区绘制新图形
            HPEN hPen = CreatePen(PS_SOLID, info.width, info.color);
            HPEN hOldPen = (HPEN)SelectObject(hDrawingDC, hPen);

            if (info.type == DrawingType::Line) {
                MoveToEx(hDrawingDC, info.pos1.x, info.pos1.y, NULL);
                LineTo(hDrawingDC, info.pos2.x, info.pos2.y);
            }
            else if (info.type == DrawingType::Rectangle) {
                Rectangle(hDrawingDC, info.pos1.x, info.pos1.y, info.pos2.x, info.pos2.y);
            }

            SelectObject(hDrawingDC, hOldPen);
            DeleteObject(hPen);

            // 触发重绘
            InvalidateRect(hwnd, NULL, FALSE);
        }
        return 0;
    }

    case WM_MOUSEMOVE:
    {
        //鼠标在客户区移动时，其实操作系统是一直发送信号的，只有当满足绘画条件时，才会执行一下操作：
        if (isDrawing && curType != DrawingType::None)
        {
            POINT currentPos = { LOWORD(lParam), HIWORD(lParam) };//获取坐标

            // 擦除旧的临时图形（使用内存缓冲区）
            if (prevPos.x != 0 && prevPos.y != 0)
            {
                // 创建临时内存DC用于擦除操作
                HDC hdcMem = CreateCompatibleDC(NULL);
                HBITMAP hbmMem = CreateCompatibleBitmap(hDrawingDC, drawingWidth, drawingHeight);
                SelectObject(hdcMem, hbmMem);

                // 复制当前已完成图形
                BitBlt(hdcMem, 0, 0, drawingWidth, drawingHeight, hDrawingDC, 0, 0, SRCCOPY);

                // 在临时DC上绘制旧的临时图形（使用背景色）
                HPEN hPenBk = CreatePen(PS_SOLID, curWidth, GetSysColor(COLOR_WINDOW));
                SelectObject(hdcMem, hPenBk);

                if (curType == DrawingType::Line)
                {
                    MoveToEx(hdcMem, ptStart.x, ptStart.y, NULL);
                    LineTo(hdcMem, prevPos.x, prevPos.y);
                }
                else if (curType == DrawingType::Rectangle)
                {
                    Rectangle(hdcMem, ptStart.x, ptStart.y, prevPos.x, prevPos.y);
                }

                // 将擦除后的内容复制到屏幕
                HDC hdc = GetDC(hwnd);
                BitBlt(hdc, 0, 0, drawingWidth, drawingHeight, hdcMem, 0, 0, SRCCOPY);
                ReleaseDC(hwnd, hdc);

                // 清理临时资源
                DeleteObject(hbmMem);
                DeleteDC(hdcMem);
                DeleteObject(hPenBk);
            }

            // 绘制新轨迹
            HDC hdc = GetDC(hwnd);
            HPEN hPen = CreatePen(PS_SOLID, curWidth, curColor);
            SelectObject(hdc, hPen);

            if (curType == DrawingType::Line)
            {
                MoveToEx(hdc, ptStart.x, ptStart.y, NULL);
                LineTo(hdc, currentPos.x, currentPos.y);
            }
            else if (curType == DrawingType::Rectangle)
            {
                Rectangle(hdc, ptStart.x, ptStart.y, currentPos.x, currentPos.y);
            }

            prevPos = currentPos;
            ReleaseDC(hwnd, hdc);
            DeleteObject(hPen);
        }
        return 0;
    }

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}